package com.xysoft.framework.orm;

import java.io.Serializable;
import java.lang.reflect.ParameterizedType;

import org.hibernate.Criteria;
import org.hibernate.Session;
import org.hibernate.SessionFactory;
import org.hibernate.engine.spi.SessionImplementor;
import org.hibernate.persister.entity.EntityPersister;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;

import com.xysoft.framework.event.HibernatePostUpdateEvent;
import com.xysoft.framework.event.HibernatePreUpdateEvent;
import com.xysoft.framework.event.PostDeleteEvent;
import com.xysoft.framework.event.PostInsertEvent;
import com.xysoft.framework.event.PreDeleteEvent;
import com.xysoft.framework.event.PreInsertEvent;

public class EntityDaoImpl<T> implements EntityDao<T>, ApplicationContextAware {
	
	private String entityName;
	private SessionFactory sessionFactory;
	private ApplicationContext applicationContext;

	public void setEntityName(String entityName) {
		this.entityName = entityName;
	}
	
	public EntityDaoImpl() {
		setEntityName(((Class<?>)((ParameterizedType)getClass().getGenericSuperclass()).getActualTypeArguments()[1]).getName());
	}

	@Override
	public String getEntityName() {
		return entityName;
	}

	public ApplicationContext getApplicationContext() {
		return applicationContext;
	}

	public void setApplicationContext(ApplicationContext applicationContext) {
		this.applicationContext = applicationContext;
	}

	public void setSessionFactory(SessionFactory sessionFactory) {
		this.sessionFactory = sessionFactory;
	}
	
	@Override
	public SessionFactory getSessionFactory() {
		return sessionFactory;
	}
	
	public Session getCurrentSession() {
		return getSessionFactory().getCurrentSession();
	}

	@SuppressWarnings("unchecked")
	@Override
	public T createNew() {
		SessionImplementor session = (SessionImplementor) getCurrentSession();
		return (T)session.instantiate(getEntityName(), null);
	}

	@Override
	public void persist(T entity) {
		EntityPersister entityPersister = (EntityPersister) getSessionFactory().getClassMetadata(getEntityName());
		Serializable identifier = entityPersister.getIdentifier(entity, (SessionImplementor)getCurrentSession());
		if(identifier == null) {
			firePreInsert(entity);
			getCurrentSession().saveOrUpdate(entity);
			firePostInsert(entity);
		} else {
			firePreUpdate(entity);
			getCurrentSession().saveOrUpdate(entity);
			firePostUpdate(entity);
		}
	}

	@Override
	public void delete(T entity) {
		firePreDelete(entity);
		getCurrentSession().delete(entity);
		firePostDelete(entity);
	}

	@SuppressWarnings("unchecked")
	@Override
	public T get(Serializable id) {
		return (T)getCurrentSession().get(getEntityName(), id);
	}

	@SuppressWarnings("unchecked")
	@Override
	public T load(Serializable id) {
		return (T)getCurrentSession().load(getEntityName(), id);
	}

	@Override
	public Criteria createQuery() {
		return getCurrentSession().createCriteria(getEntityName());
	}
	
	protected void firePreInsert(T entity) {
		if(applicationContext != null) {
			applicationContext.publishEvent(new PreInsertEvent(this, getEntityName(), entity));
		}
	}
	
	protected void firePostInsert(T entity) {
		if(applicationContext != null) {
			applicationContext.publishEvent(new PostInsertEvent(this, getEntityName(), entity));
		}
	}
	
	protected void firePreUpdate(T entity) {
		if(applicationContext != null) {
			applicationContext.publishEvent(new HibernatePreUpdateEvent(this, getEntityName(), entity, getCurrentSession()));
		}
	}
	
	protected void firePostUpdate(T entity) {
		if(applicationContext != null) {
			applicationContext.publishEvent(new HibernatePostUpdateEvent(this, getEntityName(), entity, getCurrentSession()));
		}
	}
	
	protected void firePreDelete(T entity) {
		if(applicationContext != null) {
			applicationContext.publishEvent(new PreDeleteEvent(this, getEntityName(), entity));
		}
	}
	
	protected void firePostDelete(T entity) {
		if(applicationContext!=null) {
			applicationContext.publishEvent(new PostDeleteEvent(this, getEntityName(), entity));
		}
	}

}
